package com.gome.spiderkit.client;

import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.concurrent.TimeoutException;

import org.apache.commons.codec.binary.Base64;
import org.zeromq.ZMQ;
import org.zeromq.ZMQ.Socket;

import ProcessRouter.MessageStruct.CommandGetFreePid;
import ProcessRouter.MessageStruct.CommandReleasePid;
import ProcessRouter.MessageStruct.MessagePackage;
import ProcessRouter.MessageStruct.RPCAnswer;
import ProcessRouter.MessageStruct.RPCRequest;

import com.google.protobuf.InvalidProtocolBufferException;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.util.JSON;

public class WebKitMQ {
	public static int DEFAULT_TIMEOUT = 30000;
	private String clientID;
	private String protocol;
	private Socket zmqSocket = null;
	private ZMQ.Poller poller = null;
	private boolean isFailed = false;
	private boolean isClosed = false;
	private int wkPid;
	
	public WebKitMQ() {
		poller = new ZMQ.Poller(1);
	}
	
	public boolean failed() {
		return isFailed;
	}
	
	public void setFailed() {
		this.isFailed = true;
	}
	
	private WebKitMQ(String clientID, String protocol, boolean isFailed, int wkPid) {
		this.clientID = clientID + "-" + Utils.getUUID();
		this.protocol = protocol;
		this.isFailed = isFailed;
		this.wkPid = wkPid;
		poller = new ZMQ.Poller(1);
		zmqSocket = GlobalConfig.getInstance().gZMQCTX().socket(ZMQ.REQ);
		zmqSocket.connect(protocol);
		poller.register(zmqSocket,ZMQ.Poller.POLLIN);
	}
	
	public WebKitMQ copySelfForNewThread() {
		WebKitMQ mq = new WebKitMQ(this.clientID, this.protocol, this.isFailed, this.wkPid);
		return mq;
	}
	
    public String getProtocol() {
		return protocol;
	}

	public int getWkPid() {
		return wkPid;
	}

	public String getLocalIP() {                                                                                                                              
        Enumeration<NetworkInterface> netInterfaces = null;
        String ip = "127.0.0.1";
        ArrayList<BasicDBObject> items = new ArrayList<BasicDBObject>();
        try {
            netInterfaces = NetworkInterface.getNetworkInterfaces();
            while (netInterfaces.hasMoreElements()) {
                NetworkInterface ni = netInterfaces.nextElement();
                String name = ni.getName().toLowerCase();
                if (name.startsWith("lo") || name.startsWith("vir") || name.startsWith("vmnet") || name.startsWith("wlan")) {
                    continue;
                }
                Enumeration<InetAddress> ips = ni.getInetAddresses();
                while (ips.hasMoreElements()) {
                    InetAddress ia = ips.nextElement();
                    if (ia instanceof Inet4Address) {
                        if (ia.getHostAddress().toString().startsWith("127")) {
                            continue;
                        } else {
                            ip = ia.getHostAddress();
                            BasicDBObject bs = new BasicDBObject();
                            bs.append("name",  name);
                            bs.append("ip", ip);
                            items.add(bs);
                        }
                    }
                }
            }
        } catch (Exception e) {
        }

        Collections.sort(items, new Comparator<BasicDBObject>() {

            public int compare(BasicDBObject o1, BasicDBObject o2) {
                return o1.getString("name").compareToIgnoreCase(o2.getString("name"));
            }
        });
        if (items.size() > 0) {
            ip = items.get(0).getString("ip");
        }
        return ip; 
    }
    
    public void initDefault(String groupName) {
    	initDefault(groupName, false);
    }
    
	public void initDefault(String groupName, boolean isStatic) {
		String ip = getLocalIP();
		
		RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();  
		String info = runtime.getName();  
		int index = info.indexOf("@");
		String pid = info.substring(0, index);
		String threadId = String.valueOf(Thread.currentThread().getId());
		clientID = ip + ":" + pid + "/" + threadId;

		zmqSocket = GlobalConfig.getInstance().gZMQCTX().socket(ZMQ.REQ);
		
		protocol = GlobalConfig.getInstance().getProtocolFromGroup(groupName, isStatic);
		if(protocol == null || protocol.isEmpty())
		{
			System.err.println("no available spider");
			isFailed = true;
			return ;
		}
		
		zmqSocket.connect(protocol);
		
		getFreePid(isStatic);
	}

	public void initWithServerProtocol(String protocol) {
		initWithServerProtocol(protocol, false);
	}
	
	public void initWithServerProtocol(String protocol, boolean isStatic)  {
		String ip = getLocalIP();
		
		RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();  
		String info = runtime.getName();  
		int index = info.indexOf("@");
		String pid = info.substring(0, index);
		String threadId = String.valueOf(Thread.currentThread().getId());
		clientID = ip + ":" + pid + "/" + threadId;
		
		zmqSocket = GlobalConfig.getInstance().gZMQCTX().socket(ZMQ.REQ);
		
		this.protocol = protocol;
		if(protocol == null || protocol.isEmpty())
		{
			System.err.println("no available spider");
			isFailed = true;
			return ;
		}

//		System.out.println("protocol:"+protocol);
		zmqSocket.connect(protocol);
		
		getFreePid(isStatic);
	}

	void getFreePid(boolean isStatic)
	{
		MessagePackage.Builder packageBuilder = MessagePackage.newBuilder();
		packageBuilder.setName(CommandGetFreePid.Builder.getDescriptor().getFullName());
		
		CommandGetFreePid.Builder commandBuilder = CommandGetFreePid.newBuilder();
		commandBuilder.setClientid(clientID);
		commandBuilder.setIsStatic(isStatic);
		commandBuilder.build().getSerializedSize();
		packageBuilder.setCommandGetFreePid(commandBuilder);
		
		MessagePackage pkg = packageBuilder.build();
		byte[] mess = pkg.toByteArray();
		
		zmqSocket.send(mess, 0);
		
		poller.register(zmqSocket, ZMQ.Poller.POLLIN);
		
		poller.poll(DEFAULT_TIMEOUT);
		
		if(poller.pollin(0))
		{
			byte[] recvByte = zmqSocket.recv();
			try {
				CommandGetFreePid pid = CommandGetFreePid.parseFrom(recvByte);
				this.wkPid = pid.getFreepid();
			} catch (InvalidProtocolBufferException e) {
				e.printStackTrace();
				this.isFailed = true;
			}
		} else {
			isFailed = true;
		}
	}
	
	protected void close()
	{
		if (isClosed)
			return ;
		
		if (!this.isFailed && this.wkPid != -1) {
			MessagePackage.Builder packageBuilder = MessagePackage.newBuilder();
			packageBuilder.setName(CommandReleasePid.Builder.getDescriptor().getFullName());
			
			CommandReleasePid.Builder commandBuilder = CommandReleasePid.newBuilder();
			commandBuilder.setClientid(clientID);
			commandBuilder.setFreepid(this.wkPid);
			
			packageBuilder.setCommandReleasePid(commandBuilder);
			
			MessagePackage pkg = packageBuilder.build();
			
			byte[] mess = pkg.toByteArray();
			
			zmqSocket.send(mess, 0);
			
			poller.poll(DEFAULT_TIMEOUT);
			
			if(poller.pollin(0)) {
				zmqSocket.recv();
			}
		}
		
		isClosed = true;
		poller.unregister(zmqSocket);
		zmqSocket.close();
	}
	
	public static String encode(String s) {
		byte[] encodeBase64;
		try {
			encodeBase64 = Base64.encodeBase64(s.getBytes("UTF-8"));
			return new String(encodeBase64);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return "";
	}
	
	public static String decode(String s) {
	    String decode = "";
	    try {
	        decode = new String( Base64.decodeBase64(s));
	    } catch (Exception e) {
	        e.printStackTrace();
	    }
	    return decode;
	}
	
	public String getReqJsonString(long pageid,long funcid,ArrayList<String> argList)
	{
		BasicDBObject reqObj = new BasicDBObject();
		reqObj.append("clientid", clientID);
		reqObj.append("pageid",String.valueOf(pageid));
		reqObj.append("funcid", funcid);
		
		int count = 0;
		for(String arg:argList)
		{
			String argKey = "arg" + String.valueOf(count);
			reqObj.append(argKey,encode(arg));
			count++;
		}
		
		return reqObj.toString();
	}
	
	public String getString(String msg)
	{
		if (isFailed)
			return "";
		
		if(msg == null || msg.isEmpty())
			return "";
		BasicDBObject obj = (BasicDBObject) JSON.parse(msg);
		if(obj == null)
			return "";
		String str = obj.getString("retval", "");
		if(str.isEmpty())
			return "";
		return decode(str);
	}
	
	public ArrayList<Long> getLongArrayList(String msg)
	{
		ArrayList<Long> list = new ArrayList<Long> ();
		if (isFailed)
			return list;
		
		String val = getString(msg);
		if(val == null)
			return null;
		BasicDBObject obj = (BasicDBObject) JSON.parse(val);
		if(obj == null)
			return null;
		BasicDBList bdlist = (BasicDBList) obj.get("array");
		if(bdlist == null || bdlist.isEmpty())
			return null;
		int size = bdlist.size();
		for(int i=0;i<size;i++)
			list.add(Long.valueOf((String) bdlist.get(i)));
		
		return list;
	}
	
	public ArrayList<BasicDBObject> getObjArrayList(String msg)
	{
		ArrayList<BasicDBObject> list = new ArrayList<BasicDBObject> (); 
		if (isFailed)
			return list;
		
		String val = getString(msg);

		if(val == null)
			return null;

		BasicDBList bdlist = (BasicDBList) JSON.parse(val);
		if(bdlist == null || bdlist.isEmpty())
			return null;

		int size = bdlist.size();
		for(int i=0;i<size;i++)
			list.add(((BasicDBObject) bdlist.get(i)));
		
		return list;
	}
	
	public long getLong(String msg)
	{
		if (isFailed)
			return -1;
		
		String val = getString(msg);
		if(val == null)
			return 0;
		return Long.valueOf(val);

	}
	
	public boolean getBoolean(String msg)
	{
		if (isFailed)
			return false;
		return getLong(msg)<=0?false:true;
	}
	
	public Rect getRect(String msg)
	{
		if (isFailed)
			return new Rect(0, 0, 0, 0);
		
		String val = getString(msg);
		if(val == null)
			return null;
		
		BasicDBObject obj = (BasicDBObject) JSON.parse(val);
		return new Rect(obj.getInt("x", 0),obj.getInt("y", 0),obj.getInt("width", 0),obj.getInt("height", 0));
	}
	
	public boolean checkAvailable() throws UnknowServerException, TimeoutException, InvalidProtocolBufferException
	{
		if (isFailed)
			return false;
		
		ArrayList<String> argList = new ArrayList<String>();
		String msg = webKitCall(0,WebKitProtocol.WEBKIT_LONG_CHECK_AVAILABLE,argList,2000);
		long ret = getLong(msg);
		return ret<=0?false:true;
	}
	
	public String webKitCall(long pageid,long funcid,ArrayList<String> argList) throws UnknowServerException, TimeoutException, InvalidProtocolBufferException
	{
		return webKitCall(pageid,funcid,argList,DEFAULT_TIMEOUT);
	}
	
	public String webKitCall(long pageid,long funcid,ArrayList<String> argList,int timeout) throws UnknowServerException, TimeoutException, InvalidProtocolBufferException
	{
		if (isFailed)
			return "";
		
		String reqMsg = getReqJsonString(pageid,funcid,argList);
		
		MessagePackage.Builder packageBuilder = MessagePackage.newBuilder();
		packageBuilder.setName(RPCRequest.Builder.getDescriptor().getFullName());
		
		RPCRequest.Builder requestBuilder = RPCRequest.newBuilder();
		requestBuilder.setClientid(this.clientID);
		requestBuilder.setAimpid(this.wkPid);
		requestBuilder.setArgs(reqMsg);
		
		packageBuilder.setRequest(requestBuilder);
		
		MessagePackage pkg = packageBuilder.build();
		
		byte[] mess = pkg.toByteArray();
		
		boolean sendRet = zmqSocket.send(mess, 0);
		if(!sendRet)
			return null;
	
		//timeout == 0,一直等待，直到有結果返回
		if(timeout == 0)
		{
			byte[] recvByte = zmqSocket.recv();
			try {
				RPCAnswer answer = RPCAnswer.parseFrom(recvByte);
				if (answer.getException()) {
					this.isFailed = true;
					throw new UnknowServerException(" call error !");
				}
				return answer.getRetval();
			} catch (InvalidProtocolBufferException e) {
				e.printStackTrace();
				this.isFailed = true;
				return "";
			}
		}
		else
		{
			if(poller.poll(timeout) == -1)
				return null;
			if(poller.pollin(0))
			{
				byte[] recvByte = zmqSocket.recv();
				try {
					RPCAnswer answer = RPCAnswer.parseFrom(recvByte);
					if (answer.getException()) {
						this.isFailed = true;
						throw new UnknowServerException(" call error !");
					}
					return answer.getRetval();
				} catch (InvalidProtocolBufferException e) {
					e.printStackTrace();
					this.isFailed = true;
					throw e;
				}
			} else {
				isFailed = true;
				throw new TimeoutException();
			}
		}
	}

	public long getPageValue(String msg,WebPageRef ref)
	{
		if (isFailed)
			return -4;
		
		String retval = getString(msg);
		BasicDBObject retObj = (BasicDBObject) JSON.parse(retval);
		if(retObj == null)
			return -2;
		String pageRef = retObj.getString("page", "0");
		String error = retObj.getString("error", "");
		ref.setRef(Long.valueOf(pageRef));
		return Long.valueOf(error);
			
	}
	
	public String getDownloadLinkReturn(String msg)
	{
		if (isFailed)
			return "";
		
		String link = "";
		
		String retval = getString(msg);
		BasicDBObject retObj = (BasicDBObject) JSON.parse(retval);
		if(retObj == null)
			return "";
		link = retObj.getString("link", "");

		return link;
	}
}
